《Android 进阶(一)》 自定义View之仪表盘进度条

1. 前言

一点一点学习自定义View,按照《Android开发艺术探索》中的说法,自定义View大致可以分为4类:

  1. 继承View重写onDraw方法;

  2. 继承ViewGroup派生特殊Layout;

  3. 继承特定View;

  4. 继承特定ViewGroup 看下第一种,制作一个简单的仪表盘进度条。

    2. 实现思路

  5. 继承View;

  6. 自定义属性值:arcColor,bgColor,arc_textColor,arc_textSize分别是前景色,背景色,进度文字颜色,进度文字字体大小;

  7. 确定弧形绘制位置和文字绘制位置

  8. 实现onDraw()方法

3. 效果

这里写图片描述

4. 知识点

4.1 MeasureSpec

MeasureSpec代表一个32位int值,高2位代表SpecMode,低30位代表SpecSize。SpecMode指的是测量模式,SpecSize指的是对应测量模式下的大小。

SpecMode有下面如下3中模式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* Measure specification mode: The parent has not imposed any constraint
* on the child. It can be whatever size it wants.
*/
public static final int UNSPECIFIED = 0 << MODE_SHIFT;

/**
* Measure specification mode: The parent has determined an exact size
* for the child. The child is going to be given those bounds regardless
* of how big it wants to be.
*/
public static final int EXACTLY = 1 << MODE_SHIFT;

/**
* Measure specification mode: The child can be as large as it wants up
* to the specified size.
*/
public static final int AT_MOST = 2 << MODE_SHIFT;
SpecMode 描述
UNSPECIFIED 父容器不对View有任何限制,系统涉及,平时很少涉及
EXACTLY 父容器已经检测出View所需要的精准大小,即SpecSize大小。对应match_parent和固定数值两种情况
AT_MOST 父容器指定一个View可用的大小。View的大小不能超过这值,具体是什么值要看View的具体实现。对应wrap_content情况

SpecSize大小单位px。

生成MeasureSpec方式如下:

1
2
3
4
5
6
7
8
public static int makeMeasureSpec(@IntRange(from = 0, to = (1 << MeasureSpec.MODE_SHIFT) - 1) int size,
@MeasureSpecMode int mode) {
if (sUseBrokenMakeMeasureSpec) {
return size + mode;
} else {
return (size & ~MODE_MASK) | (mode & MODE_MASK);
}
}

获取size和mode的方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
* Extracts the mode from the supplied measure specification.
*
* @param measureSpec the measure specification to extract the mode from
* @return {@link android.view.View.MeasureSpec#UNSPECIFIED},
* {@link android.view.View.MeasureSpec#AT_MOST} or
* {@link android.view.View.MeasureSpec#EXACTLY}
*/
@MeasureSpecMode
public static int getMode(int measureSpec) {
//noinspection ResourceType
return (measureSpec & MODE_MASK);
}

/**
* Extracts the size from the supplied measure specification.
*
* @param measureSpec the measure specification to extract the size from
* @return the size in pixels defined in the supplied measure specification
*/
public static int getSize(int measureSpec) {
return (measureSpec & ~MODE_MASK);
}

4.2 View的工作流程

过程 描述
onMeasure 测量过程,确定View的宽度和高度,传入的参widthMeasureSpec,heightMeasureSpec
onLayout 布局过程,确定View的上下左右四个角的位置
onDraw 绘制过程,一般用来绘制特殊图形

继承View的自定义View基本只需要关注onDraw()和onMeasure()。由于没有子View所以不需要关注onLayout()。

4.3 图形绘制

具体请查看Canvas类和Paint类,这两个类内容比较多,可以自己研究一下,有很多有用的东西。

4.3.1 Canvas绘制方法

这里写图片描述

4.3.2 Paint属性值

Paint枚举值
这里写图片描述

设置属性方法
这里写图片描述

5. 关键代码

5.1 attrs.xml

1
2
3
4
5
6
<declare-styleable name="ArcView">
<attr name="arcColor" format="color"/>
<attr name="bgColor" format="color"/>
<attr name="arc_textColor" format="color"/>
<attr name="arc_textSize" format="dimension"/>
</declare-styleable>

5.2 ArcView

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
public class ArcView extends View {

private final int MAX_SWEEP_ANGLE = 240;
private final int START_SWEEP_ANGLE = 150;
private final int DEFAULT_MAX_PROGRESS = 100;

private final int DEFAULT_ARC_COLOR = Color.RED;
private final int DEFAULT_BG_COLOR = Color.DKGRAY;
private final int DEFAULT_TEXT_COLOR = Color.BLACK;
private final int DEFAULT_TEXT_SIZE = 40;

private int mArcColor = DEFAULT_ARC_COLOR;
private int mBgColor = DEFAULT_BG_COLOR;
private int mTextColor = DEFAULT_TEXT_COLOR;
private int mTextSize = DEFAULT_TEXT_SIZE;

private int progress = 0;
private int mMaxProgress = DEFAULT_MAX_PROGRESS;

private Paint mCirclePaint;
private Paint mBgPaint;
private Paint mTextPaint;

private final Rect mTextBound = new Rect();

public ArcView(Context context) {
this(context, null);
}

public ArcView(Context context,
@Nullable AttributeSet attrs) {
this(context, attrs, 0);
}

public ArcView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
final TypedArray attributes = getContext().obtainStyledAttributes(attrs,
R.styleable.ArcView);

mArcColor = attributes
.getColor(
R.styleable.ArcView_arcColor,
DEFAULT_ARC_COLOR);

mBgColor = attributes
.getColor(
R.styleable.ArcView_bgColor,
DEFAULT_BG_COLOR);

mTextColor = attributes
.getColor(
R.styleable.ArcView_arc_textColor,
DEFAULT_TEXT_COLOR);

mTextSize = (int) attributes
.getDimension(
R.styleable.ArcView_arc_textSize,
DEFAULT_TEXT_SIZE);
attributes.recycle();
init();
}

private void init() {
mCirclePaint = new Paint();
mCirclePaint.setColor(mArcColor);
mCirclePaint.setStrokeWidth(8.0F);
mCirclePaint.setDither(true);
mCirclePaint.setAntiAlias(true);
mCirclePaint.setStyle(Paint.Style.STROKE);


mBgPaint = new Paint();
mBgPaint.setColor(mBgColor);
mBgPaint.setStrokeWidth(20.0F);
mBgPaint.setAntiAlias(true);
mBgPaint.setStyle(Paint.Style.STROKE);
mBgPaint.setStrokeCap(Paint.Cap.ROUND);

mTextPaint = new Paint();
mTextPaint.setStrokeWidth(4);

//字体SP单位转换成PX
int size = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP,
mTextSize, getResources().getDisplayMetrics());
mTextPaint.setTextSize(size);
mTextPaint.setAntiAlias(true);
mTextPaint.setColor(mTextColor);
mTextPaint.setTextAlign(Paint.Align.LEFT);
}

@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
int paddingLeft = getPaddingLeft();
int paddingRight = getPaddingRight();
int paddingTop = getPaddingTop();
int paddingBottom = getPaddingBottom();

int circleWidth = getWidth() - paddingLeft - paddingRight;
int circleHeight = getHeight() - paddingTop - paddingBottom;

int radius = Math.min(circleWidth, circleHeight) / 2;

int left = getLeft() + paddingLeft;
int right = left + radius * 2;
int top = getTop() + getPaddingTop();
int bottom = top + 2 * radius;
canvas.drawArc(left, top, right, bottom, START_SWEEP_ANGLE, MAX_SWEEP_ANGLE, false,
mBgPaint);

int sweepArc = MAX_SWEEP_ANGLE * progress / mMaxProgress;
canvas.drawArc(left, top, right, bottom, START_SWEEP_ANGLE, sweepArc, false, mCirclePaint);

String text = String.valueOf(progress) + "%";
mTextPaint.getTextBounds(text, 0, text.length(),
mTextBound);
canvas.drawText(text, (left + right) / 2 - mTextBound.width() / 2,
(top + bottom) / 2 + mTextBound.height() / 2, mTextPaint);
}

/**
* 设置进度大小
*/
public void setProgress(int progress) {
if (progress < 0 || progress > mMaxProgress) {
return;
}
this.progress = progress;
invalidate();
}

/**
* 设置最大进度值
*/
public void setMaxProgress(int maxProgress) {
this.mMaxProgress = maxProgress;
}
}

6. 源码

源码已上传Github,后续自定义View的学习Demo也会陆续上传

CustomViewDemo

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×